# -*- coding: UTF-8 -*-

# @Author :YTL
# @Time   :2020/5/27 19:24
# @File   :parse_systrace_tool.py

import sys
import os
import re
import datetime
import math


BaseDir=os.path.dirname(__file__)
every_frame_time = 17 #如果是60fps的则是16.67,90fps则是11.11
#打印总开关,调试用，如果不需要输出可以置为False
DEBUG = False

def read_systrace_html(file_name=None):
	if file_name is None or file_name == "":
		file_name = sys.argv[1]
	system_server_pid, animator_pid = auto_anylize_pid(file_name)
	print("system_server_pid and animator_pid is %s and %s"  %(system_server_pid, animator_pid))

	#将相关的有用的tracing_mark_write: B、tracing_mark_write: E写入到临时文件中，然后在进行帧数和总时间的解析
	generate_temp_systrace_file(file_name, system_server_pid, animator_pid)


def getCurrentTime():
	return  datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
	pass


def auto_anylize_pid(file_name):
	system_server_pid = 0
	animator_pid  = 0
	pattern_system_server = r'system_server pid=(\d+)\s*prio='
	pattern_animator = r'tracing_mark_write: B\|(\d+)\|animation|animator'

	with open(file_name, encoding='utf-8', mode='r') as f1:
		system_server_pid_str = "system_server pid="
		while True:
			line = f1.readline()
			if not line:
				break
			if system_server_pid_str in line:
				m = re.compile(pattern_system_server).search(line)
				if m:
					system_server_pid = m.group(1)
					if DEBUG:
						print("system_server_id is " + str(system_server_pid))
					break
	with open(file_name, encoding='utf-8', mode='r') as f2:
		animator_pid_str = "tracing_mark_write: B"
		while True:
			line = f2.readline()
			if not line:
				break
			if animator_pid_str in line:
				m = re.compile(pattern_animator).search(line)
				if m:
					animator_pid = m.group(1)
					if DEBUG:
						print("animator_pid is " + str(animator_pid))
					break

	if animator_pid == 0:
		raise ("animator_pid not found, normaly exit.")

	return system_server_pid, animator_pid
	pass



def generate_temp_systrace_file(file_name, system_server_pid, animator_pid):
	'''
	将同一个进程相关的内容输出到一个临时的文件，然后在进行帧率解析，解析完成后然后删除
	:param file_name:
	:param system_server_pid:
	:param animator_pid:
	:return:
	'''
	global out_temp_systrace_file
	pattern_processId = '-' + animator_pid  #需要解析的相关字段
	u1, *_ = file_name.split(".")
	out_temp_systrace_file = u1 + "_" + animator_pid +"_temp.txt"
	BEGIN_TRACE = '<!-- BEGIN TRACE -->'
	END_TRACE = '<!-- END TRACE -->'
	temp_f = open(out_temp_systrace_file, encoding='utf-8', mode='w+')
	with open(file_name, encoding='utf-8', mode='r') as f:
		temp_f.write(BEGIN_TRACE)
		temp_f.write("\n")
		while True:
			line = f.readline()
			if pattern_processId in line:
				temp_f.write(line)
			elif line == "":
				print("out_temp_systrace_file generate finished.")
				break
		pass
	temp_f.write(END_TRACE)
	try:
		temp_f.close()
	except:
		print("generate_temp_systrace_file: temp_f closed failure")

	ayalyse_frame_data(out_temp_systrace_file, animator_pid)
	pass


def ayalyse_frame_data(out_temp_systrace_file, animator_pid):
	'''
	该类是解析类，解析方发只要看该类即可
	:param out_temp_systrace_file:
	:param animator_pid:
	:return:
	'''
	total_frames = 0
	list_frame_start_time = [] #保存每帧的开始时间
	list_frame_end_time = []  #保存每帧的结束时间，开始和结束时间是一一对应的
	pattern_start_frame = "tracing_mark_write: B\\|" + animator_pid + "\\|Choreographer#doFrame"
	pattern_start2_frame = "tracing_mark_write: B\\|" + animator_pid
	pattern_end_frame = "tracing_mark_write: E\\|" + animator_pid

	#栈，保证BE是对应的,通过入栈和出栈判断每帧的起始时间、结束时间、帧数
	frames_stack = []  #出栈入栈的思想

	#第一帧的控制变量
	isFirstFrameFlag = True
	#每一帧控制的变量，在帧头为True，帧尾为false，结束计算,如果不使用该变量则每帧时间会变大，不完全是Choreographer#doFrame时间
	isFrameFlag = False
	#保存出栈的变量，当出栈并且下一个是Choreographer#doFrame,则是栈尾
	stack_temp_frame_line = ""

	with open(out_temp_systrace_file, encoding='utf-8', mode='r') as f:
		while True:
			line = f.readline()
			if line == "":
				break
			if isFirstFrameFlag:
				if re.compile(pattern_start_frame).search(line) is not None: #找到第一帧和第一帧的开始时间
					isFrameFlag = True
					frames_stack.append(line)  # 找到第一个，入栈
					total_frames += 1
					start_time = getTimeFromLine(line)  #第一帧开始时间，也是测试开始时间，也是每一帧的开始时间
					list_frame_start_time.append(start_time)
					isFirstFrameFlag = False
			elif re.compile(pattern_start_frame).search(line) is not None:  #找到第2、3、N帧
				isFrameFlag = True
				frame_start_time = getTimeFromLine(line)
				list_frame_start_time.append(frame_start_time) #找到前一帧的最后一个出栈帧
				if DEBUG:
					print("======打印结束帧=====")
					print(stack_temp_frame_line)
				frame_end_time = getTimeFromLine(stack_temp_frame_line)  #每一帧的结束时间，最后一帧是总的结束时间
				list_frame_end_time.append(frame_end_time)
				if len(frames_stack) != 0:  #说明在最后的BE数量不匹配，测试结束
					break
				frames_stack.append(line)  # 将B开头的入栈，碰到E出栈
				total_frames += 1
			elif re.compile(pattern_start2_frame).search(line) is not None: #遇到"tracing_mark_write: B"就入栈
				if isFrameFlag:
					frames_stack.append(line)  # 将B开头的入栈，碰到E出栈
			elif re.compile(pattern_end_frame).search(line) is not None:  #遇到"tracing_mark_write: E"就出栈
				if isFrameFlag and len(frames_stack) != 0:
					frames_stack.pop()  #不空则出栈
				#等于0时说明出栈完毕，同时该帧计算完毕
				if isFrameFlag and len(frames_stack) == 0:
					isFrameFlag = False
					stack_temp_frame_line = line  # 保存最后一个要出的E，然后在Choreographer#doFrame做计算
				#当B和E不成对的时候，说明这个时候Choreographer#doFrame(Did Not Finish),systrace的表现，暂时不知道为什么
				#出现该种情况则测试结束，后续的不需要渲染，在首帧上判断是否 len(frames_stack) != 0
				pass
	
	#测试输入的
	if DEBUG:
		if len(list_frame_start_time) == len(list_frame_end_time):
			for item in list(zip(list_frame_start_time, list_frame_end_time)):
				print(item)
			pass
		elif len(list_frame_start_time) > len(list_frame_end_time):
			length = len(list_frame_end_time)
			for item in list(zip(list_frame_start_time[0:length], list_frame_end_time)):
				print(item)
			pass
		elif len(list_frame_start_time) < len(list_frame_end_time):
			length = len(list_frame_start_time)
			for item in list(zip(list_frame_start_time, list_frame_end_time[0:length])):
				print(item)
			pass
		else:
			print("起始结束帧不匹配，起始帧数%d：结束帧数%d" %(len(list_frame_start_time), len(list_frame_end_time)))
	pass
	#删除生成的临时输出文件
	delete_file(out_temp_systrace_file)
	
	#生成结果文件
	generate_frame_result(list_frame_start_time, list_frame_end_time, animator_pid)


def generate_frame_result(list_frame_start_time, list_frame_end_time, animator_pid):
	if len(list_frame_start_time) > len(list_frame_end_time):
		list_frame_start_time = list_frame_start_time[0:-2]
	elif len(list_frame_start_time) < len(list_frame_end_time):
		list_frame_end_time = list_frame_end_time[0:-2]
	
	#保存输出结果的最终文件，查看该文件可以到详细结果
	ayalyse_result_framestate = BaseDir + "/" + str(getCurrentTime()) + "_" + str(animator_pid) + "_result_framestate.txt"
	
	# 第一帧开始时间，也是测试开始时间
	# 最后一帧结束时间，也是测试结束时间 ，总的测试时间 total_times= end_time - start_time ，则帧率= total_frames/total_times
	# 每一帧的开始时间，每一帧的结束时间， 帧花费时间  = frame_end_time - frame_start_time
	total_frames = len(list_frame_start_time)
	total_times = round((float(list_frame_end_time[-1]) - float(list_frame_start_time[0]))/1e6, 3)
	fps = round(total_frames/total_times, 2) #帧率
	#每一帧Choreographer#doFrame花费的时间
	list_every_frames_time = [round((list_frame_end_time[i] - list_frame_start_time[i])/1e3,3) for i in range(len(list_frame_start_time))]
	# 计算丢帧数量列表，下一帧的开始减去上一帧的开始时间，如果超过16.67ms则有丢帧,以及2帧之间是否丢3帧
	#list_lost_frames = list(map(lambda x: x[0]-x[1], zip(list_frame_start_time[1:-1], list_frame_start_time[0:-2])))
	list_lost_frames = [list_frame_start_time[i+1] - list_frame_start_time[i] for i in range(len(list_frame_start_time)-1)]
	if DEBUG:
		print(list_lost_frames)
	list_lost_frames = [math.floor(t/1e3/every_frame_time) for t in list_lost_frames]  #转换成ms要除以1e3
	is_lost_3_frames = "是" if max(list_lost_frames)>=3 else "否"
	if DEBUG:
		print(list_every_frames_time)
		print(list_lost_frames)
	with open(ayalyse_result_framestate, encoding='utf-8', mode='w') as f:
		line1 = "总帧数：" + str(total_frames) + "\n"
		line2 = "总测试耗时：" + str(total_times) + "S" + "\n"
		line3 = "帧率：" + str(fps) + " fps""\n"
		line4 = "是否有连续丢3帧：" + is_lost_3_frames + "\n"
		f.writelines([line1, line2, line3, line4])
		print(line1, line2, line3, line4, end='\n')
		
		f.write("\n")
		f.write("=" * 8 + "丢帧统计情况" + "=" * 8 + "\n")
		for i, item in enumerate(list_lost_frames, 0):
			if list_lost_frames[i] != 0:
				f.write("第%d帧和%d之间丢了:%d帧" %(i+1, i+2, list_lost_frames[i]))
				f.write("\n")
		
		f.write("\n")
		f.write("=" * 8 + "每帧耗时情况" + "=" * 8 + "\n")
		for i, item in enumerate(list_every_frames_time, 0):
			f.write("第%d帧时间：%.3f" %(i+1, list_every_frames_time[i]))
			f.write("\n")
	print("\n解析的详细内容请查看同目录下的 %s 文件\n" %ayalyse_result_framestate)
	
	if ("win" in str.lower(sys.platform)):
		os.system('notepad %s' %ayalyse_result_framestate)
	pass


def delete_file(file_name):
	if os.path.exists(file_name):
		os.remove(file_name)
	pass


def getTimeFromLine(line):
	#将解析的时间换成 ms 的时间
	pattern_time = ".*\s(\d+).(\d+):.*"
	m = re.compile(pattern_time).search(line)
	if m:
		return int(m.group(1))*1e6 + int(m.group(2))
	return 0


def main():
	assert len(sys.argv) == 2, print("请输入解析的完整路径文件")
	file_name = sys.argv[1]
	if file_name == os.path.basename(file_name):
		file_name = os.path.join(os.path.dirname(file_name), file_name)
	else:
		print("请将解析文件放入脚本同目录，或输入完成的路径")
	read_systrace_html(file_name)
	pass


def test():
	file_name = r'C:\Users\YTL\PycharmProjects\Systrace\trace_2020_05_24_09_43_01.html'
	file_name2 = r'C:\Users\YTL\PycharmProjects\Systrace\测试机trace.html'
	read_systrace_html(file_name)
	pass


if __name__ == '__main__':
	main()